local skynet = require "skynet"
local cluster = require "skynet.cluster"
require "skynet.manager"

local configdao = require "configdao"
local timetool = require "timetool"
local filelog = require "filelog"
local tabletool = require "tabletool"
local table_insert = table.insert
local table_remove = table.remove
local ipairs = ipairs
local pairs = pairs

require "dzpkenum"
local queue = require "skynet.queue"
local cs = queue()

local router
local CMD = {}

local matchtable_conf = {{}, {}, {}}
--匹配桌
local match_table = { 
    idle_table = {},    -- 空闲匹配桌
    full_table = {},    -- 满人匹配桌
}

local wait_rolelist = {}
local match_list = {{}, {}, {}}
local LIST_LEN = 2
local LINE_LEN = 1
local ALL_SEAT_LEN = 3
local NEED_SEAT_LEN = 3
local TIME_LIMIT = 5
local TIME_MAX = 300
local ROW = 2
local factor = 0
local slice = math.ceil(LIST_LEN/ROW)
local lock = false

--
--获得所有超时匹配
--
local function handle_outime_player(mlist, mtype)
    local curtime = timetool.get_time()
    --检测超时匹配
    local rematch, kicked = {}, {}
    local lasttime, level, ip
    local ips = {}
    for i, sublist in ipairs(mlist) do
        if #sublist > 0 then
            for _, rid in pairs(sublist) do
                if rid and wait_rolelist[rid] then
                    lasttime = wait_rolelist[rid].ts
                    level = wait_rolelist[rid].level
                    ip = wait_rolelist[rid].roledata.ip_addr or ''
                    if curtime - lasttime >= TIME_MAX then  --超过最终超时时间，踢出匹配队列
                        table_insert(kicked, rid)
                    -- elseif curtime - lasttime >= TIME_LIMIT then --超过TIME_LIMIT还未匹配成功，立即处理
                    --     if i ~= 1 then  -- 把所有限时内没匹配成功的玩家全部放入1队列
                    --         table_insert(rematch, rid)
                    --     end
                    else
                        if not ips[ip] then
                            table_insert(rematch, rid)
                            -- ips[ip] = 1
                            if #rematch % ALL_SEAT_LEN == 0 then
                                ips = {}
                            end
                        end
                    end
                end
            end
        end
    end
    if #kicked > 0 then
        for _, rid in ipairs(kicked) do
            CMD.cancelmatch(rid)
        end
    end
    if #rematch > 2 then
        local rids = {}
        local n = 1
        for _, rid in ipairs(rematch) do
            CMD.remove_from_matchqueue(rid, mtype, true)
            -- CMD.entermatchtable(rid, {}, {}, 1) -- 把所有限时内没匹配成功的玩家全部放入1队列
            rids[n] = rids[n] or {}
            table_insert(rids[n], rid)
            if #rids[n] == ALL_SEAT_LEN then
                n = n + 1
            end
        end
        for _, v in ipairs(rids) do
            if #v == ALL_SEAT_LEN then
                CMD.enter_table(v, mtype)
            else
                for _, rid in ipairs(v) do
                    CMD.entermatchtable(rid, {}, {}, 1) -- 把所有限时内没匹配成功的玩家全部放入1队列
                end
            end
        end
    end
end

--
--检测匹配超时
--超过一定时间仍在匹配队列的玩家，立即对其进行匹配处理
--
local function check_match_outtime()
    local status
    while true do
        if lock then
            skynet.sleep(1 * 100)
        else
            for i = 1, LIST_LEN do  --检测每一个子队列，剔除所有超时的匹配
                status = pcall(handle_outime_player, match_list[i], i)
                if not status then
                    filelog.sys_error("check_match_outtime pcall error!")
                end
            end
            --挂起一段时间，然后继续检测
            skynet.sleep(TIME_LIMIT * 100)
        end
    end
end

function CMD.init(caller)
    router = caller -- 这里的caller，是router
    matchtable_conf = tabletool.deepcopy(configdao.get_common_conf("matchtable_conf"))   --初始化匹配桌配置
    LIST_LEN = #matchtable_conf
    slice = math.ceil(LIST_LEN/ROW)
    for i = 1, LIST_LEN do  --初始化匹配队列
        match_list[i] = {}
        for k = 1, LINE_LEN do
            match_list[i][k] = {}
        end
        --初始化空闲桌
        match_table.idle_table[i] = {}
    end

    skynet.fork(check_match_outtime)
end

local function sendmsg_to_players(msgname, rids, ...)
    if type(rids) ~= type{} then
        rids = {rids}
    end
    local nodename = skynet.getenv("nodename")
    nodename = string.sub(nodename,1, string.len(nodename) - 4)
    cluster.send("gatenode", ".router", "notify", rids, nodename .. "." .. msgname, ...)
end

local function sendmsg_to_gate(msgname, rid, ...)
    local nodename = skynet.getenv("nodename")
    nodename = string.sub(nodename, 1, string.len(nodename) - 4)
    cluster.send("gatenode", ".router", msgname, rid, nodename, ...)
end

--
--加入匹配桌
--自动坐下，准备
--
local function do_sitdown(table_service, table_index, rid, roledata)
    local args = {}
    args.rid = rid
    args.playerinfo = roledata.info
    args.playerinfo.ip_addr = roledata.ip_addr
    args.playerinfo.gps_addr = roledata.gps_addr
    args.money = roledata.money
    skynet.pcall(skynet.call, table_service, "lua", "sitdowntable", args)
end

local function do_ready(table_service, table_index, rid, roledata)
    local args = {}
    args.rid = rid
    args.playerinfo = roledata.info
    args.playerinfo.ip_addr = roledata.ip_addr
    args.money = roledata.money
    skynet.pcall(skynet.call, table_service, "lua", "gameready", args)
end

--
--创建匹配场
--
local function create_match_table(level, table_conf)
    level = level or EMatchType.MATCH_TYPE_PRIMARY
    table_conf = table_conf or matchtable_conf[level]
    if not table_conf then
        filelog.sys_error("create_match_table: config err!")
    end
    local roledata = {
        info = { 
            rolename = "管理员",--名字
            level = 0,          --等级
            logo = "1",         --头像
            sex = 2,            --性别
            rid = 101,
        },
        money = {
            diamond = 99999999,
            fangka = 9999999,
            coin = 99999999,
            lock_diamond = 0,
        },
        ip_addr = "",
    }
    table_conf.match_type = level
    table_conf.game_room_type = 2   -- EGameRoomType.ROOM_TYPE_MATCH_ZUANSHI
    local msgbody = {config = table_conf}

    return router.createtable(roledata.info.rid, roledata, msgbody)
end

--
--创建一些匹配桌
--tablenum 创建数量
--
local function create_some_match_table(level, tablenum)
    tablenum = tablenum or 10
    if tablenum <= 0 then
        return
    end

    local idle_match_table = match_table.idle_table[level]
    local table_conf = matchtable_conf[level]
    for i = 1, tablenum do
        local new_table = create_match_table(level, table_conf)
        if type(new_table) == type{} and new_table.table_index then
            idle_match_table[new_table.table_index] = new_table.table_index
        else
            filelog.sys_error("create_some_match_table: there is not enough table service!", new_table)
        end
    end
end

--
--获得空闲匹配桌
--level 匹配桌级别（1初级 2中级 3高级）
-- nums 获得空闲桌数量,默认1
--
local function get_idle_matchtable(level)
    local idletable = {}
    local index_to_codes = router.get_gamenode_codes()
    local num = 0
    for k, v in pairs(match_table.idle_table[level]) do
        match_table.idle_table[level][k] = nil
        if index_to_codes[k] and not match_table.full_table[k] then
            table_insert(idletable, k)
            num = num + 1
            break
        end
    end

    --桌子不够，创建匹配桌，默认创建10个
    if num == 0 then
        create_some_match_table(level, 5)
        for k, v in pairs(match_table.idle_table[level]) do
            match_table.idle_table[level][k] = nil
            if index_to_codes[k] and not match_table.full_table[k] then
                table_insert(idletable, k)
                break
            end
        end
    end
    return idletable
end

-- inner use
function CMD.enter_table(roles, level)
    local idletable = get_idle_matchtable(level)
    if #idletable == 0 then
        for _, rid in ipairs(roles) do
            sendmsg_to_gate("role_end_match", rid)   --通知gate，结束匹配
            wait_rolelist[rid] = nil
        end
        filelog.sys_error("enter_table: no table!")
        return
    end

    local index_to_codes = router.get_gamenode_codes()
    local table_map = router.get_gamenode_data()
    local table_index = table.remove(idletable)
    if not index_to_codes[table_index] then
        filelog.sys_error("match enter_table:error ===>", table_index, index_to_codes, idletable)
        return
    end
    local table_service = table_map[index_to_codes[table_index]].table_service
    local rids = {}
    local ips = {}
    for _, rid in ipairs(roles) do
        local roledata = wait_rolelist[rid].roledata
        sendmsg_to_gate("role_end_match", rid)   --通知gate，结束匹配

        local result = router.entertable(rid, roledata, {table_index = table_index})    --进桌
        if result and not result.retcode then
            -- sitdown 和 ready要分开
            do_sitdown(table_service, table_index, rid, roledata)   --自动坐下
            table_insert(rids, rid)
            table_insert(ips, roledata.ip_addr)
        else
            filelog.sys_error('match enter_table failed:', result)
        end
    end
    for _, rid in ipairs(rids) do
        -- do_ready(table_service, table_index, rid, wait_rolelist[rid].roledata)   --自动准备
        wait_rolelist[rid] = nil
    end
    sendmsg_to_players("matchresultN", rids, {table_index = table_index})  --通知客户端匹配结果
    match_table.full_table[table_index] = {table_index = table_index, level = level, ips = ips}
end

local function check_player(rid, type)
    if not matchtable_conf[type] then
        return false
    end
    local enter_diamond = matchtable_conf[type].enter_diamond
    local status, result = skynet.pcall(cluster.call,"gatenode",".router","check_role_state", rid, enter_diamond)
    if not status then
        return false
    end
    if result then  -- 在桌子内，直接踢掉
        return false
    end
    return true
end

local function insert_match_list(rid, type, line)
    lock = true
    if not line then
        line = factor%LINE_LEN + 1
        factor = factor + 1
    end
    table_insert(match_list[type][line], rid) -- 分配
    lock = false
    --[[ check
    local list = match_list[type][line]
    local rids = {}
    if #list >= NEED_SEAT_LEN then -- 单列满足桌子需求人数，进桌
        for i = 1, NEED_SEAT_LEN do
            table_insert(rids, table_remove(list, 1))
        end
        CMD.enter_table(rids, type)
    end
    -]]
    -- filelog.debug("match_list:", match_list)
end

local function match(rid, roledata, type, rematch)
    if not rid or not roledata or type < 1 or type > LIST_LEN then
        return {retcode = EErrCode.ERR_INVALID_PARAMS,retstr = "无效的请求"}
    end
    local ts = timetool.get_time()
    if not rematch then
        if wait_rolelist[rid] then
            filelog.sys_error("match: have be in mathlist!")
            return
        end
        wait_rolelist[rid] = {roledata = roledata, ts = ts, level = type}
    end
    cs(insert_match_list, rid, type, rematch)
    sendmsg_to_gate("role_start_match", rid)
    return {timeout_time = ts + TIME_MAX}
end

--
--进入匹配桌
--
function CMD.entermatchtable(rid, roledata, request, rematch)
    local responsemsg = {}
	if router.will_kill then
		responsemsg.retcode = EErrCode.ERR_INVALID_REQUEST
		responsemsg.retstr = "服务器维护中"
		return responsemsg
	end
    local level
    if rematch then
        if not wait_rolelist[rid] then
            return {retcode = 0, retstr = ""}
        end
        level = wait_rolelist[rid].level
        roledata = wait_rolelist[rid].roledata
    else
        if type(request) ~= type{} or not request.match_type or not matchtable_conf[request.match_type] then
            return {retcode = EErrCode.ERR_INVALID_PARAMS, retstr = "参数错误！"}
        end
        if wait_rolelist[rid] then
            return {retcode = 0, retstr = ""}
        end
        if router.get_role_data(rid) then
            return {retcode = EErrCode.ERR_HAD_IN_TABLE, retstr = "已经在游戏中了！"}
        end
        local enter_diamond = matchtable_conf[request.match_type].enter_diamond
        if enter_diamond > roledata.money.diamond then
            return {retcode = EErrCode.ERR_NOT_ENOUGH_DIAMOND, retstr = "元宝数量不足！"}
        end
        if not check_player(rid, request.match_type) then
            return {retcode = EErrCode.ERR_HAD_IN_TABLE, retstr = "已经在其他游戏中了！"}
        end
        level = request.match_type
    end

    --开始匹配
    roledata.gps_addr = request.gps_addr
	return match(rid, roledata, level or 1, rematch)
end

local function remove_player(rid, level, line)
    lock = true
    if not line then
        wait_rolelist[rid] = nil
    end
    local list = match_list[level]
    local found = false
    for _, sublist in ipairs(list) do   -- 列表很短，消耗不大
        for i, v in ipairs(sublist) do
            if v == rid then
                found = true
                table_remove(sublist, i)
                lock = false
                return true
            end
        end
    end
    if not found then   -- 理论上队列应该是个数组，上面就能直接找到，但未知的意外情况导致数组不连续，这里做兼容
        for _, sublist in ipairs(list) do   -- 列表很短，消耗不大
            for i, v in pairs(sublist) do
                if v == rid then
                    found = true
                    sublist[i] = nil
                    lock = false
                    return true
                end
            end
        end
    end
    lock = false
    return true
end

--
--从匹配队列删除
--level 队列级别
--
function CMD.remove_from_matchqueue(rid, level, line)
    cs(remove_player, rid, level, line)
end

--
--取消匹配
--
function CMD.cancelmatch(rid, roledata, request)
    if router.get_role_data(rid) then
        -- filelog.sys_error("cancelmatch:" .. tostring(rid) .. "已经匹配成功，不能取消匹配！")
        return {retcode = EErrCode.ERR_HAD_IN_TABLE, retstr = "已经匹配成功，不能取消匹配！"}
    end
    if wait_rolelist[rid] == nil then
        return {retcode = 0, retstr = ""}
    end

    --取消匹配
    local level = wait_rolelist[rid].level
    local result = CMD.remove_from_matchqueue(rid, level)
    sendmsg_to_gate("role_end_match", rid)   --通知gate，结束匹配
	return {}
end

--
--重新进入匹配桌
--
function CMD.reentermatchtable(rid, roledata, request)
    if wait_rolelist[rid] == nil then
        sendmsg_to_gate("role_end_match", rid)   --通知gate，结束匹配
        return {}
    end

    local level = wait_rolelist[rid].level
    
    --检测元宝是否足够
    local diamond = roledata.money.diamond or 0
    if diamond < matchtable_conf[level].enter_diamond then
        CMD.cancelmatch(rid, roledata, request)
        return {retcode = EErrCode.ERR_NOT_ENOUGH_DIAMOND, retstr = "元宝数量不足！"}
    end

    --更新roledata
    local responsemsg = {}
    roledata.gps_addr = request.gps_addr
    wait_rolelist[rid].roledata = roledata
    responsemsg.match_type = level
    responsemsg.timeout_time = wait_rolelist[rid].ts + TIME_LIMIT

	return responsemsg
end

--
--检测是否为匹配桌
--
function CMD.check_is_matchtable(table_index)
    local index_to_codes = router.get_gamenode_codes()
    if not table_index or table_index < 1 or not index_to_codes[table_index] then
        return false
    end

    if match_table.full_table[table_index] then
        return true
    end

    for i, idle_table in pairs(match_table.idle_table) do
        if idle_table[table_index] then
            return true
        end
    end
    
    return false
end

local function get_player_num(type)
    local num = 0
    for i, v in ipairs(match_list[type]) do
        num = num + #v
    end
    return num
end

--
--获得匹配桌信息
--
function CMD.get_matchlist()
    local matchlist = { }
    for i, conf in pairs(matchtable_conf) do
        matchlist[i] = {
            match_type 	    = i > slice and (i - slice) or i,        --匹配场类型 1初级 2中级 3高级（级别越高底注越高）
	        dizhu  		    = conf.base_diamond,	        --底注
	        ruchang		    = conf.enter_diamond, 	        --入场钻石
	        lichang		    = conf.leave_diamond,	        --离场钻石
	        conf_intro      = conf,                         --匹配桌配置
	        max_player      = conf.max_player_num,	        --桌子最大人数                         
        }

        --该匹配场当前总人数：游戏中 + 匹配中
        matchlist[i].cur_total_player = get_player_num(i)
    end

    --获得游戏中人数
    local index_to_codes = router.get_gamenode_codes()
    for k,item in pairs(match_table.full_table) do
        local i = item.level
        if index_to_codes[k] then
            matchlist[i].cur_total_player = matchlist[i].cur_total_player + matchlist[i].max_player
        else
            match_table.full_table[k] = nil
            match_table.idle_table[i][k] = nil
        end
    end
    return matchlist
end

function CMD.get_playing_player()
    local num = 0
    for i in pairs(matchtable_conf) do
        num = num + get_player_num(i)
    end
    return num
end

local function check_ip(ips, rid)
    if not ips or not rid then return false end
    if wait_rolelist[rid] then
        for _, ip in pairs(ips) do
            if ip == (wait_rolelist[rid].roledata.ip_addr or '') then
                return false
            end
        end 
    end
    return true
end

local function enter_playing_table(rid, table_index, mtype)
    if not rid or not wait_rolelist[rid] then
        return
    end
    local roledata = wait_rolelist[rid].roledata
    if not roledata then
        return
    end
    sendmsg_to_gate("role_end_match", rid)   --通知gate，结束匹配
    wait_rolelist[rid] = nil
    if not check_player(rid, mtype) then
        return
    end

    local index_to_codes = router.get_gamenode_codes()
    local table_map = router.get_gamenode_data()
    if not index_to_codes[table_index] then
        return
    end
    local table_service = table_map[index_to_codes[table_index]].table_service
    local result = router.entertable(rid, roledata, {   --进桌
        table_index = table_index,
        match_type = mtype,
    })
    if result and not result.retcode then
        -- sitdown 和 ready要分开
        do_sitdown(table_service, table_index, rid, roledata)   --自动坐下
        -- do_ready(table_service, table_index, rid, roledata)   --自动准备
        sendmsg_to_players("matchresultN", rid, {table_index = table_index})  --通知客户端匹配结果
    end
end

--
--向未满的桌子推匹配等待的玩家
--
function CMD.add_waiting_player(index, mtype)
    if not index or not mtype then
        return
    end
    local line, list
    for i = 1, LINE_LEN do
        line = ((factor - 1) > 0 and (factor - 1) or 0)%LINE_LEN + 1
        list = match_list[mtype][line]
        if type(list) == type{} and #list > 0 then
            factor = 0
            break
        end
        factor = factor + 1
    end
    if not line or not list or #list == 0 then
        return 
    end

    if match_table.full_table[index] then   -- 现在只有一条线，有多条线的时候再考虑是否检查类型列表
        for i, v in ipairs(list) do
            if check_ip(match_table.full_table[index].ips, v) then
                local rid = table_remove(list, i)
                enter_playing_table(rid, index, mtype)
                break
            end
        end
    end

    -- local rid = table_remove(list, 1)
    -- enter_playing_table(rid, index, mtype)
end

--
--重新开始匹配
--
function CMD.restart_matchtable(rids, match_type)
    if not rids or #rids == 0 then
        return
    end

    --由客户端再次发送匹配请求
    local nodename = skynet.getenv("nodename")
    nodename = string.sub(nodename,1, string.len(nodename) - 4)
    sendmsg_to_players("restartmatchN", rids, {match_type = match_type, nodename = nodename})  --通知客户端
end

function CMD.clear_full_matchtable(table_index)
    match_table.full_table[table_index] = nil
end

function CMD.reset_matchqueue()
    for rid, item in pairs(wait_rolelist) do
        CMD.cancelmatch(rid, item.roledata)
    end
    for i = 1, LIST_LEN do  --初始化匹配队列
        match_list[i] = {}
        for k = 1, LINE_LEN do
            match_list[i][k] = {}
        end
    end
    wait_rolelist = {}
end

function CMD.get_wait_rolelist()
    return {match_list, wait_rolelist}
end

return CMD